import threading
import time
from functools import wraps

import eventlet

from util.user_log import log
from selenium.common.exceptions import TimeoutException, WebDriverException
from decimal import Decimal


class MyError(Exception):
    def __init__(self, value):
        log.error(value)
        self.value = value

    def __str__(self):
        s01 = '\n'
        return s01 + self.value


class ArgsError(Exception):
    def __init__(self, value=None):
        log.error(value)


class BaseUtil:
    @staticmethod
    def is_ele(func):
        @wraps(func)
        def run(self, *args, **kwargs):
            # 获取第一个参数，传入另一个实例方法，把这个参数分解成元素和定位信息
            # 元素传给func，定位信息用来写日志
            try:
                ele, data = self.is_element(args[0])
            except IndexError:
                raise MyError('ele不能通过关键字参数传入，必须用位置参数传入\n{}')
            log.debug('{}元素：{}'.format(BaseUtil.log_by_name(func.__name__), data))
            # 获取参数列表，把元组转换成列表，然后更换第一个参数（关键字参数除外）
            args = list(args)
            args[0] = ele
            return func(self, *args, **kwargs)

        return run

    @staticmethod
    def log_by_name(name):
        d01 = {'click': '【点击】',
               'send_keys': '【输入】',
               'get_attribute': '【获取元素属性】',
               'execute_script': '【执行js脚本】',
               'move_to_element': '【鼠标悬停】',
               'double_click': '【双击】',
               'clear': '【清除文本】'}
        return d01.get(name)

    @staticmethod
    def rerun(func):
        @wraps(func)
        def run(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except TimeoutException as err:
                raise err
            except WebDriverException:
                log.error('错误：WebDriverException；函数 %s()'
                          '；稍作等待然后再次运行' % func.__name__)
                time.sleep(0.5)
                try:
                    return func(*args, **kwargs)
                except WebDriverException:
                    raise WebDriverException('\n错误：可能元素正处于不可点击状态')

        return run

    @staticmethod
    def assert_func(expect, result, way='=='):
        func = lambda x: str(x) if isinstance(x, (int, float, bool)) else x
        expect, result = map(func, (expect, result))
        msg = '【执行断言】：期望结果，实际结果, 断言方式分别为：' \
              '\n{}  ||格式：{}\n{}  ||格式：{}\n{}'.format(
            expect, type(expect),
            result, type(result), way)
        try:
            if way == '==':
                assert expect == result
            elif way == 'in':
                assert expect in result
            elif way == 'list2list':
                assert set(expect).issubset(result)
            else:
                raise ArgsError('【断言方式错误】：没有这种断言方式"{}"'.format(way))
        except AssertionError:
            raise MyError('【断言错误】\n{}'.format(msg))
        else:
            log.debug('【断言通过】\n{}'.format(msg))

    @staticmethod
    def time_out(wait_time, msg=None):
        eventlet.monkey_patch()
        return eventlet.Timeout(wait_time, MyError(msg))


def mark(msg=''):
    log.info('【标记】' + msg)


class MyThread(threading.Thread):
    def __init__(self, target, group=None, name=None,
                 args=(), kwargs=None, *, daemon=None):
        super().__init__(group=group, name=name, args=args, kwargs=kwargs, daemon=daemon)
        self.target = target
        self.args = args

    def run(self):
        # 不能放在构造函数里, 会变慢
        self.__res = self.target(*self.args)

    def get_res(self):
        # 获取线程返回值
        try:
            return self.__res  # 如果子线程不使用join方法，此处可能会报没有self.result的错误
        except AttributeError:
            raise AttributeError('线程返回值获取错误！不能没有join')


def thread_join(params=None, threads=None, func_main=None):
    # params = [(api01, 1), (api02, 2)]
    t01 = None
    if params:
        t01 = [MyThread(target=i[0], args=(i[1:]), daemon=True) for i in params]
    elif threads:
        t01 = threads
    [i.start() for i in t01]
    [i.join() for i in t01]
    res_list = [i.get_res() for i in t01]
    if func_main:
        return func_main(*res_list)
    else:
        return res_list


if __name__ == '__main__':
    util = BaseUtil
    with util.time_out(1, msg='超时'):
        time.sleep(10)
